#include "PrecompiledHeader.h"
#include "Mesh.h"

/*--------------------------------------------------------------------------*/

Mesh::Mesh(const std::string& filename) :
	IResource("mesh:" + filename)
{
	this->m_setsCount = 0;
	this->m_path = filename;
	this->m_scaling = 0;

	this->m_boundingBox = NULL;
}

/*--------------------------------------------------------------------------*/

Mesh::~Mesh()
{
	if (this->m_boundingBox)
	{
		ROpenGL::DeleteVertexArray(&this->m_boundingBox);
	}

	for (auto i = this->m_sets.begin(); i != this->m_sets.end(); ++i)
	{
		ROpenGL::DeleteVertexArray(&i->m_vao);
	}
}

/*--------------------------------------------------------------------------*/

void Mesh::Reload()
{
	Lib3dsFile* model = lib3ds_file_load(this->m_path.c_str());
	AssertLogE(model == NULL, "Can not load file: %s", this->m_path);

	this->UpdateScalingAndBoundingBox(model);

	for (Lib3dsMesh* mesh = model->meshes; mesh != NULL; mesh = mesh->next)
	{
		/*------genereowanie normali------------------------------------------------*/

		Lib3dsVector* normals = new Lib3dsVector[mesh->faces * 3];
		lib3ds_mesh_calculate_normals(mesh, normals);

		/*----generowanie VB--------------------------------------------------------*/

		GLVertexBuffer* VB = ROpenGL::GenBuffer("VB:" + this->m_path + "/MESH:" + mesh->name, EVertexDeclaration::BasicVertex, mesh->faces * 3);

		BasicVertex* ptnVB = (BasicVertex*) ROpenGL::MapBuffer(VB);
		uint vert = 0;

		for (uint cur_face = 0; cur_face < mesh->faces; ++cur_face)
		{
			Lib3dsFace* face = &mesh->faceL[cur_face];

			for (uint i = 0; i < 3; i++)
			{
				memcpy(&ptnVB[vert].position, &mesh->pointL[face->points[ i ]].pos, sizeof(Lib3dsVector));
				ptnVB[vert].position *= this->m_scaling;

				memcpy(&ptnVB[vert].texcoord, mesh->texelL[face->points[ i ]], sizeof(Lib3dsTexel));
				memcpy(&ptnVB[vert].normal, &normals[vert], sizeof(Lib3dsVector));
				vert++;
			}
		}

//
// 		for (uint vert = 0; vert < mesh->points; ++vert)
// 		{
// 			glm::vec3* v = (glm::vec3*) mesh->pointL[vert].pos;
// 			glm::vec3* t = (glm::vec3*) mesh->texelL[vert];
//
// 			ptnVB[vert].texcoord = *t;
// 			ptnVB[vert].position = (*v * this->m_scaling);
//
// 			ptnVB[vert].normal.x = normals[vert][0];
// 			ptnVB[vert].normal.y = normals[vert][1];
// 			ptnVB[vert].normal.z = normals[vert][2];
// 		}

		ROpenGL::UnmapBuffer(VB);
		delete [] normals;

		/*--------generowanie IB----------------------------------------------------*/
//
// 		GLVertexBuffer* IB = ROpenGL::GenBuffer("IB:" + this->m_path + "/MESH:" + mesh->name, GL_ELEMENT_ARRAY_BUFFER, mesh->faces * 3 * sizeof(uint), GL_STATIC_DRAW);
// 		uint* ptnIB = (uint*) ROpenGL::MapBuffer(IB);
//
// 		for (uint cur_face = 0; cur_face < mesh->faces; ++cur_face)
// 		{
// 			Lib3dsFace* face = &mesh->faceL[cur_face];
//
// 			ptnIB[3 * cur_face + 0] = (uint) face->points[0];
// 			ptnIB[3 * cur_face + 1] = (uint) face->points[1];
// 			ptnIB[3 * cur_face + 2] = (uint) face->points[2];
// 		}
//
// 		ROpenGL::UnmapBuffer(IB);

		/*-------generowanie VAO----------------------------------------------------*/

		GLVertexArray* VAO = ROpenGL::GenVertexArray("VAO:" + this->m_path + "/MESH:" + mesh->name, GL_TRIANGLES, mesh->faces * 3);
		ROpenGL::BindVertexArray(VAO);

		ROpenGL::AttachBuffer(VB);
		//ROpenGL::AttachIndexBuffer(IB);

		ROpenGL::UnbindVertexArray();

		ROpenGL::UniqueBufferOwner(VAO, VB);
//		ROpenGL::UniqueBufferOwner(VAO, IB);

		/*-------pobieranie materialu-----------------------------------------------*/

		Lib3dsMaterial* meshMaterial = NULL;

		for (Lib3dsMaterial* material = model->materials; material != NULL; material = material->next)
		{
			if (std::strcmp(mesh->faceL[0].material, material->name) == 0)
			{
				meshMaterial = material;
				break;
			}
		}

		AssertLogC(meshMaterial == NULL, "Mesh %s is without assigned material.", mesh->name);

		/*--------upychanie materialu do enginowej obudowy--------------------------*/

		std::stringstream ss;
		ss << "material_" << this->m_path << "_" << mesh->name;

// 		Material* material = new Material();
// 		g_Material->AddMaterial(ss.str(), material);
//		delete material;
//		material = const_cast<Material*>(g_Material->GetMaterial(ss.str()));

		/*--------------------------------------------------------------------------*/
//
// 		ShaderRes* shader = NULL;
// 		g_Resources->Get("ds_firstPassShader", &shader);
// 		material->SetShader(shader->GetShader());
//
// 		g_Resources->Get("ds_firstPassInstancedShader", &shader);
// 		material->SetInstancedShader(shader->GetShader());
//
// 		material->SetShininess(100.0f);
// 		material->SetBumpfactor(0.0f);
//
// 		/*---------uzupelnianie materialu danymi z 3ds------------------------------*/
//
// 		material->SetAmbient(Color(meshMaterial->ambient[0],
// 		                           meshMaterial->ambient[1],
// 		                           meshMaterial->ambient[2],
// 		                           meshMaterial->ambient[3]));
//
// 		material->SetDiffuse(Color(meshMaterial->diffuse[0],
// 		                           meshMaterial->diffuse[1],
// 		                           meshMaterial->diffuse[2],
// 		                           meshMaterial->diffuse[3]));
//
// 		material->SetSpecular(Color(meshMaterial->specular[0],
// 		                            meshMaterial->specular[1],
// 		                            meshMaterial->specular[2],
// 		                            meshMaterial->specular[3]));
//
// 		material->SetShininess(meshMaterial->shininess);

		/*-----------laczenie materialu z vao---------------------------------------*/

		MaterialVaoSet mvset(NULL, VAO);

		/*--------doczytanie tekstur do materialu-----------------------------------*/

// 		if (meshMaterial->texture1_map.name != "")
// 		{
// 			std::stringstream sss;
// 			sss << ::GetFilePath(this->m_path) << meshMaterial->texture1_map.name;
//
// 			//LogI(1) << "Loading material for " << this->GetPath() << "@" << mesh << " texture1: " << meshMaterial->texture1_map.name << " texture2: " << meshMaterial->texture2_map.name;
//
// 			GLTexture* t = ROpenGL::GetTexture(sss.str());
//
// 			if (t == NULL)
// 			{
// 				t = ROpenGL::GenTexture(sss.str());
// 				ROpenGL::LoadTextureFromFile(t, sss.str());
// 			}
//
// 			TextureRes* q;
// 			g_Resources->Get("grass1", &q);
//
// 			material->AddTexture(q->GetTexture(), "texture0");
// 			material->AddTexture(q->GetTexture(), "bumpmapping");
// 		}

		this->m_sets.push_back(mvset);
	}

	lib3ds_file_free(model);
	model = NULL;

	this->m_setsCount = this->m_sets.size();
}

/*--------------------------------------------------------------------------*/

const std::vector<MaterialVaoSet>& Mesh::GetSets() const
{
	return this->m_sets;
}

/*--------------------------------------------------------------------------*/

uint Mesh::GetSetsCount() const
{
	return this->m_setsCount;
}

/*--------------------------------------------------------------------------*/

MeshInstance* Mesh::CreateInstance() const
{
	return new MeshInstance(NodeCreation(0, NULL, NULL), this);
}

/*--------------------------------------------------------------------------*/

const std::string& Mesh::GetPath() const
{
	return this->m_path;
}

/*--------------------------------------------------------------------------*/

void Mesh::SetPath(const std::string& path)
{
	this->m_path = path;
}

/*--------------------------------------------------------------------------*/

void Mesh::UpdateScalingAndBoundingBox(Lib3dsFile* model)
{
	glm::vec3 maxscaling = glm::vec3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
	glm::vec3 minscaling = glm::vec3(FLT_MAX, FLT_MAX, FLT_MAX);

	for (Lib3dsMesh* mesh = model->meshes; mesh != NULL; mesh = mesh->next)
	{
		for (uint cur_face = 0; cur_face < mesh->faces; ++cur_face)
		{
			Lib3dsFace* face = &mesh->faceL[cur_face];

			for (uint i = 0; i < 3; i++)
			{
				maxscaling.x = MAX(maxscaling.x, mesh->pointL[face->points[ i ]].pos[0]);
				maxscaling.y = MAX(maxscaling.y, mesh->pointL[face->points[ i ]].pos[1]);
				maxscaling.z = MAX(maxscaling.z, mesh->pointL[face->points[ i ]].pos[2]);

				minscaling.x = MIN(minscaling.x, mesh->pointL[face->points[ i ]].pos[0]);
				minscaling.y = MIN(minscaling.y, mesh->pointL[face->points[ i ]].pos[1]);
				minscaling.z = MIN(minscaling.z, mesh->pointL[face->points[ i ]].pos[2]);
			}
		}
	}

	m_scaling = MAX(m_scaling, abs(maxscaling.x));
	m_scaling = MAX(m_scaling, abs(maxscaling.y));
	m_scaling = MAX(m_scaling, abs(maxscaling.z));

	m_scaling = MAX(m_scaling, abs(minscaling.x));
	m_scaling = MAX(m_scaling, abs(minscaling.y));
	m_scaling = MAX(m_scaling, abs(minscaling.z));

	m_scaling = 1.0f / m_scaling;

	this->m_maxNegative = minscaling * m_scaling;
	this->m_maxPositive = maxscaling * m_scaling;

	/*--------------------------------------------------------------------------*/

	GLVertexBuffer* vb = ROpenGL::GenBuffer("VB_BOUNDINGBOX:" + this->m_path, EVertexDeclaration::Position0, 8);
	glm::vec3* v = (glm::vec3*) ROpenGL::MapBuffer(vb);
	v[0] = glm::vec3(this->m_maxNegative.x, this->m_maxNegative.y, this->m_maxNegative.z);
	v[1] = glm::vec3(this->m_maxNegative.x, this->m_maxNegative.y, this->m_maxPositive.z);
	v[2] = glm::vec3(this->m_maxNegative.x, this->m_maxPositive.y, this->m_maxNegative.z);
	v[3] = glm::vec3(this->m_maxNegative.x, this->m_maxPositive.y, this->m_maxPositive.z);
	v[4] = glm::vec3(this->m_maxPositive.x, this->m_maxNegative.y, this->m_maxNegative.z);
	v[5] = glm::vec3(this->m_maxPositive.x, this->m_maxNegative.y, this->m_maxPositive.z);
	v[6] = glm::vec3(this->m_maxPositive.x, this->m_maxPositive.y, this->m_maxNegative.z);
	v[7] = glm::vec3(this->m_maxPositive.x, this->m_maxPositive.y, this->m_maxPositive.z);
	ROpenGL::UnmapBuffer(vb);

	GLVertexBuffer* ib = ROpenGL::GetBuffer("CUBEIB");
	AssertLogC(ib == NULL, "CUBEIB VertexBuffer not found!");

	this->m_boundingBox = ROpenGL::GenVertexArray("VAO_BOUNDINGBOX:" + this->m_path, GL_TRIANGLES, 36);
	ROpenGL::BindVertexArray(this->m_boundingBox);

	ROpenGL::AttachBuffer(vb);//, 0, 3, GL_FLOAT, 0, 0);
	//ROpenGL::AttachBuffer(vb);//, 2, 3, GL_FLOAT, 0, 0);
	ROpenGL::AttachBuffer(ib);

	ROpenGL::UnbindVertexArray();
	ROpenGL::UnbindBuffers();

	ROpenGL::UniqueBufferOwner(this->m_boundingBox, vb);
}

/*--------------------------------------------------------------------------*/

GLVertexArray* Mesh::GetBoundingBox() const
{
	return this->m_boundingBox;
}

/*--------------------------------------------------------------------------*/
